﻿/*    
Nemulation: A .NET based framework/platform for virtualization applications
Copyright (C) 2011 Bryan Perris

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace NEmuVirtualHost.Services
{
    public sealed class ServicesProvider : IDictionary<Type, ServiceHost>
    {
        private HashSet<Type> m_ServiceTypeHash;
        private List<ServiceHost> m_Hosts;

        // Events
        public event EventHandler<ServiceHostEventArgs> HostOpened;
        public event EventHandler<ServiceHostEventArgs> HostClosed;

        public ServicesProvider()
        {
            m_Hosts = new List<ServiceHost>();
            m_ServiceTypeHash = new HashSet<Type>();
        }

        #region Public Members

        public void AddService(Type serviceType)
        {
            if (serviceType == null)
                throw new ArgumentNullException("serviceType");

            if (!ContainsKey(serviceType))
            {
                Add(serviceType, new ServiceHost(serviceType));
            }
            else
            {
                throw new ArgumentException("A service with the same type already exists.");
            }
        }

        public void AddService(object serviceInstance)
        {
            if (serviceInstance == null)
                throw new ArgumentNullException("serviceInstance");

            if (!ContainsKey(serviceInstance.GetType()))
            {
                Add(serviceInstance.GetType(), new ServiceHost(serviceInstance));
            }
            else
            {
                throw new ArgumentException("A service with the same type already exists.");
            }
        }

        public void AddService(ServiceReflector serviceReflector)
        {
            foreach (Type serviceType in serviceReflector)
            {
                AddService(serviceType);
            }
        }

        public void RunServices()
        {
            foreach (KeyValuePair<Type, ServiceHost> pair in this)
            {
                ServiceHost host = pair.Value;

                if (host != null)
                {
                    if (host.State != CommunicationState.Opened)
                    {
                        try
                        {
                            host.Open(new TimeSpan(0, 0, 30));
                            OnHostOpened(host);
                        }
                        catch (Exception)
                        {
                            continue;
                        }
                    }
                }
            }
        }

        public void CloseServices()
        {
            foreach (KeyValuePair<Type, ServiceHost> pair in this)
            {
                ServiceHost host = pair.Value;

                if (host != null)
                {
                    if (host.State == CommunicationState.Opened || host.State == CommunicationState.Opening)
                    {
                        try
                        {
                            host.Close(new TimeSpan(0, 0, 60));
                            OnHostClosed(host);
                        }
                        catch (Exception)
                        {
                            continue;
                        }
                    }
                }
            }
        }

        #endregion

        private void OnHostOpened(ServiceHost host)
        {
            if (HostOpened != null)
                HostOpened(this, new ServiceHostEventArgs(host));
        }

        private void OnHostClosed(ServiceHost host)
        {
            if (HostClosed != null)
                HostClosed(this, new ServiceHostEventArgs(host));
        }

        private int GetIndex(Type key)
        {
            for (int i = 0; i < m_Hosts.Count; i++)
            {
                if (m_Hosts[i].Description.ServiceType.Equals(key))
                    return i;
            }

            return -1;
        }

        private ServiceHost GetValue(Type key)
        {
            int index = GetIndex(key);

            if (index >= 0 && index < m_Hosts.Count)
            {
                return m_Hosts[index];
            }

            return null;
        }

        #region IDictionary<> Memebers

        public void Add(Type key, ServiceHost value)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            if (value == null)
                throw new ArgumentNullException("value");

            m_ServiceTypeHash.Add(key);
            m_Hosts.Add(value);
        }

        public bool ContainsKey(Type key)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            return m_ServiceTypeHash.Contains(key);
        }

        public ICollection<Type> Keys
        {
            get { return m_ServiceTypeHash.ToList<Type>(); }
        }

        public bool Remove(Type key)
        {
            if (this.IsReadOnly)
                throw new NotSupportedException();

            if (key == null)
                throw new ArgumentNullException("key");

            if (ContainsKey(key))
            {
                int index = GetIndex(key);

                if (index == -1)
                    return false;

                m_Hosts.RemoveAt(index);
                m_ServiceTypeHash.Remove(key);

                return true;
            }
            else
            {
                return false;
            }
        }

        public bool TryGetValue(Type key, out ServiceHost value)
        {
            if (key == null)
                throw new ArgumentNullException("key");

            ServiceHost host = GetValue(key);

            if (host == null)
            {
                value = null;
                return false;
            }
            else
            {
                value = host;
                return true;
            }
        }

        public ICollection<ServiceHost> Values
        {
            get { return m_Hosts; }
        }

        public ServiceHost this[Type key]
        {
            get
            {
                return GetValue(key);
            }
            set
            {
                m_Hosts[GetIndex(key)] = value;
            }
        }

        public void Add(KeyValuePair<Type, ServiceHost> item)
        {
            if (IsReadOnly)
                throw new NotSupportedException();

            m_ServiceTypeHash.Add(item.Key);
            m_Hosts.Add(item.Value);
        }

        public void Clear()
        {
            m_Hosts.Clear();
            m_ServiceTypeHash.Clear();
        }

        public bool Contains(KeyValuePair<Type, ServiceHost> item)
        {
            ServiceHost host;

            if (TryGetValue(item.Key, out host))
            {
                if (host.Equals(item.Value))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        public void CopyTo(KeyValuePair<Type, ServiceHost>[] array, int arrayIndex)
        {
            if (array == null)
                throw new ArgumentNullException("array");

            if (arrayIndex < 0)
                throw new ArgumentOutOfRangeException("arrayIndex");

            if (m_ServiceTypeHash.Count > (array.Length - arrayIndex))
                throw new ArgumentException("not enough space in the array");

            int i = arrayIndex;

            foreach (Type key in m_ServiceTypeHash)
            {
                array[i++] = new KeyValuePair<Type, ServiceHost>(key, GetValue(key));
            }
        }

        public int Count
        {
            get { return m_ServiceTypeHash.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(KeyValuePair<Type, ServiceHost> item)
        {
            if (Contains(item))
            {
                Remove(item.Key);
                return true;
            }
            else
            {
                return false;
            }
        }

        public IEnumerator<KeyValuePair<Type, ServiceHost>> GetEnumerator()
        {
            return new ServicesProviderEnumerator(this);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }
}
